package searcher;


import java.io.*;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: HUAWEI
 * Date: 2023-04-19
 * Time: 8:59
 */
public class Parser {
    //先指定一个加载文档的路径
    private static final String INPUT_PATH = "C:\\jdk-8u361-docs-all\\docs\\api";
    //创建一个Index实例
    private Index index = new Index();

    private AtomicLong t1 = new AtomicLong();
    private AtomicLong t2 = new AtomicLong();


    //用run方法对整个流程进行封装
    //通过这个方法实现“单线程制作索引”
    public void run() {
        long beg =System.currentTimeMillis();
        System.out.println("索引制作开始");
        // static整个parser类的入口；
        //1.根据上面指定的路径来枚举出该路径中所有的文件(html)这个过程需要把所有子目录中的文件都能获取到；
        ArrayList<File> fileList = new ArrayList<File>();
        //INPUT_PATH 是表示从哪一个目录开始，fileList 表示最后结果存当哪里；
        enumFile(INPUT_PATH,fileList);
        long endEnumFile = System.currentTimeMillis();
        System.out.println("枚举文件完毕！消耗时间：" + (endEnumFile-beg));
//        System.out.println(fileList);
//        System.out.println(fileList.size());
        //2.针对上面罗列出的文件路径，打开文件，读取文件内容，并进行解析，并构建索引；
        //通过for循环来遍历枚举的
        for(File f : fileList) {
            //通过这个方法来解析单个 HTML 文件
            System.out.println("开始解析 ：" +f.getAbsolutePath());
            parseHTML(f);
            
        }
        long endFor = System.currentTimeMillis();
        System.out.println("遍历文件完毕：" + (endFor - endEnumFile));
        //以上都是在内存中构造的
        //3.把内存中构造好的索引数据结构，保存到指定文件中；这个步骤需要调用Index实例
        index.save();
        long end = System.currentTimeMillis();
        System.out.println("索引制作完毕！消耗时间：" + (end-beg)+"ms");
    }

    //通过这个方法实现“多线程制作索引”
    public void runByThread() throws InterruptedException {
        long beg = System.currentTimeMillis();
        System.out.println("索引制作开始：");
        //1.枚举出所有文件的
        ArrayList<File> files = new ArrayList<>();
        enumFile(INPUT_PATH,files);

        //2.循环遍历文件，此处为了能够通过多线程制作索引，就直接引入线程池;
        CountDownLatch latch = new CountDownLatch(files.size());
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for(File f :files){
            executorService.submit(new Runnable() {
                //描述具体内部类
                @Override
                public void run() {
                    System.out.println("解析：" + f.getAbsolutePath());
                    parseHTML(f);
                    latch.countDown();
                }
            });
        }
        //await方法会阻塞，直到所有的选手都调用countDown 撞线后才能阻塞结束
        latch.await();
        //手动地把线程池里面的线程都干掉
        executorService.shutdown();
        //3.保存索引
        index.save();

        long end = System.currentTimeMillis();
        System.out.println("索引制作完毕！消耗时间：" + (end - beg) + "ms");
        System.out.println("t1 + " + t1 + " ,t2: " + t2);


    }

    private void parseHTML(File f) {
        //1.解析出 HTML 的标题
        String title = parseTitle(f);
        //2.解析出 HTML 对应的 URL
        String Url = parseUrl(f);
        //3.解析出 HTML 对应的正文（有了正文才有后续的描述
        long beg = System.nanoTime();
        String content = parseContent(f);
        long mid = System.nanoTime();
        //4.把解析出来的信息，加入到索引当中
        index.addDoc(title,Url,content);
        long end = System.nanoTime();
        //parseHTML会被循环调用很多次，单次调用时间较短
        //加入频繁的打印会拖慢这个速度本身
        t1.addAndGet(mid - beg);
        t2.addAndGet(end - mid);
    }
        //为了从TestParseContent直接调用此方法，使用public
    public String parseContent(File f) {
        //先按照一个字符一个字符的方式获取，以< 和 > 来控制拷贝数据的开关;
        //手动把缓冲区设置成1M大小
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(f),1024*1024)){; //后面是缓冲区大小
            //加上一个是否要进行拷贝的开关；
            boolean isCopy = true;
            //还得准备一个保存结果的 stringBuilder
            StringBuilder content = new StringBuilder();
            while(true) {
                //按理说一次读一个字符，返回char，read返回的是int，
                //此处使用int做返回值，是为了表示一些非法情况；
                //如果读到文件末尾，继续读就会返回-1；
                int ret = bufferedReader.read();
                if(ret == -1) {
                    //表示文件读完了
                    break;
                }
                //如果这个结果不是-1，那么就是一个合法的字符；
                char c = (char)ret;
                 if(isCopy) {
                     //开关打开的状态，遇到普通字符就应该拷贝到 StringBuilder 中
                     if(c == '<'){
                         //关闭开关
                         isCopy = false;
                         continue;
                     }
                     if(c == '\n' || c == '\r'){
                         //为了去掉换行，把换行替换成空格
                        c = ' ';
                     }
                         //其他字符直接进行拷贝即可；把最终字符拷贝到最终的 StringBuilder 中;
                         content.append(c);
                 } else {
                     //开关关闭的状态，就暂时不拷贝，直到遇到 >
                     if(c == '>') {
                         isCopy = true;
                     }
                 }
            }
            FileReader fileReader;
            fileReader.close();
            return content.toString();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return "";
    }

    public String readFile(File f) {
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(f))) {
            StringBuilder stringBuilder = new StringBuilder();
            while (true) {
                int ret = bufferedReader.read();
                if(ret == -1) {
                    break;
                }
                char c =(char)ret;
                if(c == '\n' || c =='\r' ) {
                    c =' ';
                }
                Object content;
                content.append(c);
            }
        }catch(IOException e) {
            e.printStackTrace();
        }
        return "";
    }



    //这个方法内部就基于正则表达式，实现去标签，以及去除script;
    public String parseContentByRegex(File f){
        //1.先把整个文件都读到String里面，然后再使用正则
        String content = readFile(f);
        //2.替换掉script标签
        content.replaceAll("<script.*?>(.*?)</script>","");
        //3.替换掉普通的html标签；
        content.replaceAll("<.*?>","");
        //4.使用正则把多个空格，合并成一个空格；
        content.replaceAll("\\s+","");
        return content;

    }


    private String parseUrl(File f) {
        String part1 = "https://docs.oracle.com/javase/8/docs/api/";
        String part2 = f.getAbsolutePath().substring(INPUT_PATH.length());
        return part1 + part2;
    }

    private String parseTitle(File f) {
        String name = f.getName();
        return name.substring(0,name.length() - ".html".length());
    }

    //第一个表示从哪个目录开始递归遍历，第二个参数表示递归得到的结果
    private void enumFile(String input_path, ArrayList<File> fileList) {
        File rootPath = new File(input_path);
        //listFiles能够获取到 rootPath 当前目录下所包含的文件/目录；
        //使用listFiles 只能看到一级目录，看不到子目录里面的内容；
        //想要看子目录里面的内容，则需要进行递归；  
        File[] files = rootPath.listFiles();
        for(File f : files) {
            //跟据 f 的类型，来决定是否需要递归；
            //如果 f 是一个普通文件，就把 f 加入到 fileList结果中；
            //如果 f 是一个目录，就递归调用 enumFile 这个方法，来进一步获取目录中的内容；
            if(f.isDirectory()) {
                enumFile(f.getAbsolutePath(),fileList);
            } else {
                if (f.getAbsolutePath().endsWith(".html")){
                    fileList.add(f);
                }
            }
        }
    }


    public static void main(String[] args) {
        //通过main方法来实现整个制作索引的过程
        Parser parser = new Parser(); //先创建出一个Parser实例；
        parser.run();//调用run方法
    }
}
